﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using Ionic.Zip;
using System.Text.RegularExpressions;

namespace ColladaViewer
{
    public partial class Form1 : Form
    {
        //private ModelViewControl modelView;
        private BackgroundWorker worker;

        /// <summary>
        /// Stores to filename of the file that's 
        /// gonna be loaded next
        /// </summary>
        private string fileToLoad;

        private bool firstRun = true;
        private bool loading = false;

        public Form1(string loadFile)
            : this()
        {
            fileToLoad = loadFile;
        }

        public Form1()
        {
            InitializeComponent();
            worker = new BackgroundWorker();

            //modelView = new ModelViewControl();
            //modelView.Dock = DockStyle.Fill;
            //this.Controls.Add(modelView);
            //this.SuspendLayout();

            this.AllowDrop = true;
            this.DragEnter += Form1_DragEnter;
            this.DragDrop += Form1_DragDrop;
            this.MouseWheel += Form1_MouseWheel;

            //this.ClientSizeChanged += Form1_ClientSizeChanged;
            //this.AutoSizeChanged += Form1_ClientSizeChanged;
            //this.SizeChanged += Form1_ClientSizeChanged;
            //this.LocationChanged += Form1_ClientSizeChanged;            

            this.Activated += Form1_Activated;

            worker.DoWork += worker_DoWork;
            worker.RunWorkerCompleted += worker_RunWorkerCompleted;
            materialsPanel1.ModelView = modelView;
        }

        private void Form1_Activated(object sender, EventArgs e)
        {
            if (fileToLoad != null && firstRun)
            {
                StartLoadingModel(fileToLoad);
                firstRun = false;
            }
        }

        private void Form1_ClientSizeChanged(object sender, EventArgs e)
        {
            if (modelView.Ready)
            {
                modelView.CalculateViewProjection();
                modelView.Invalidate();                
            }
        }

        private void Form1_MouseWheel(object sender, MouseEventArgs e)
        {
            modelView.Zoom((float)e.Delta / 1000.0f);
            modelView.Invalidate();
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            toolStripProgressBar1.Value = 0;
            toolStripProgressBar1.Style = ProgressBarStyle.Blocks;
            toolStripStatusLabel1.Text = "Ready.";
            modelView.Invalidate();

            this.label2.Text = modelView.NumPolygons + "";
            this.label4.Text = modelView.NumMeshes + "";
            this.label6.Text = modelView.NumMaterials + "";            

            loading = false;

            if (modelView.Model == null)
            {
                toolStripStatusLabel1.Text = "Laden des Modells fehlgeschlagen";
                return;
            }

            animationPanel1.Init(modelView.Model, modelView);     
            materialsPanel1.Init(modelView.Model.Data.Effects.Values, 
                Path.GetDirectoryName(fileToLoad));
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                LoadModel(fileToLoad);                
            }
            catch (FileNotFoundException ex)
            {
                MessageBox.Show("File was not found: " + ex.FileName,
                                "Error: File not found", MessageBoxButtons.OK,
                                MessageBoxIcon.Error);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\r\n\r\n" + ex.StackTrace,
                                "Unexpected Error", MessageBoxButtons.OK,
                                MessageBoxIcon.Error);
            }
        }

        void Form1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
                e.Effect = DragDropEffects.Copy;            
        }

        void Form1_DragDrop(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            string colladaFile = null;

            foreach (string file in files)
            {
                if (Path.GetExtension(file).ToLower() == ".dae")
                {
                    colladaFile = file;
                    break;
                }
                else if (Path.GetExtension(file).ToLower() == ".zip")
                {
                    colladaFile = ExtractFilesFromZIP(file);                    
                    break;
                }
            }

            if (colladaFile == null)
            {
                MessageBox.Show("Unsupported file format");
            }
            else
            {                
                StartLoadingModel(colladaFile);
            }
        }

        string ExtractFilesFromZIP(string zipFilename)
        {
            ZipFile zip = new ZipFile(zipFilename);
            string temp = Path.GetTempPath() + "\\ColladaViewer";                       
            
            zip.ExtractAll(temp, ExtractExistingFileAction.OverwriteSilently);
            string dae = zip.EntryFileNames.
                Where(name => name.ToLower().Contains(".dae")).
                FirstOrDefault();

            if (dae != null)
                dae = temp + "\\" + dae;

            // Adjust image paths, if necessary
            string content = File.ReadAllText(dae);            

            var matches = Regex.Matches(content, "<init_from>(.*\\.(png|jpeg|jpg|tga|bmp|gif))</init_from>", 
                RegexOptions.Multiline | RegexOptions.IgnoreCase);            

            foreach (Match match in matches)
            {
                if (!match.Success) continue;

                foreach (Capture capture in match.Captures)
                {
                    string image = capture.Value.Replace("<init_from>", "").
                                                 Replace("</init_from>", "");

                    if (!File.Exists(image))
                    {
                        // Look for file in temp folder
                        string name = Path.GetFileName(image);                        
                        string[] results = Directory.GetFiles(temp, "*.*", 
                            SearchOption.AllDirectories);

                        var find = (from f in results 
                                    where f.Contains(name) 
                                    select f).FirstOrDefault();

                        if (find != null)
                        {
                            string rel = RelativePath(temp, find);                            
                            content = content.Replace(image, rel);
                        }
                        else
                        {
                            MessageBox.Show("Error: Referenced image '" +
                                            image + "' was not found");
                            return null;
                        }
                    }
                }                              

                // Update paths in COLLADA file
                File.WriteAllText(dae, content);                
            }            

            return dae;
        }

        private string RelativePath(string absolutePath, string relativeTo)
        {
            string[] absoluteDirectories = absolutePath.Split('\\');
            string[] relativeDirectories = relativeTo.Split('\\');

            //Get the shortest of the two paths
            int length = absoluteDirectories.Length < relativeDirectories.Length ? absoluteDirectories.Length : relativeDirectories.Length;

            //Use to determine where in the loop we exited
            int lastCommonRoot = -1;
            int index;

            //Find common root
            for (index = 0; index < length; index++)
                if (absoluteDirectories[index] == relativeDirectories[index])
                    lastCommonRoot = index;
                else
                    break;

            //If we didn't find a common prefix then throw
            if (lastCommonRoot == -1)
                throw new ArgumentException("Paths do not have a common base");

            //Build up the relative path
            StringBuilder relativePath = new StringBuilder();

            //Add on the ..
            for (index = lastCommonRoot + 1; index < absoluteDirectories.Length; index++)
                if (absoluteDirectories[index].Length > 0)
                    relativePath.Append("..\\");

            //Add on the folders
            for (index = lastCommonRoot + 1; index < relativeDirectories.Length - 1; index++)
                relativePath.Append(relativeDirectories[index] + "\\");
            relativePath.Append(relativeDirectories[relativeDirectories.Length - 1]);

            return relativePath.ToString();
        }

        void LoadModel(string filename)
        {            
            modelView.LoadModel(filename);            
        }

        private void wireframeToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            modelView.WireframeMode = wireframeToolStripMenuItem.Checked;
            modelView.Invalidate();
        }

        private void boundingBoxToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            modelView.ShowBoundingBox = boundingBoxToolStripMenuItem.Checked;
            modelView.Invalidate();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();    
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Title = "Open COLLADA file";
            dialog.Filter = "COLLADA Files|*.dae|XML Files|*.xml|ZIP Files|*.zip";
            dialog.Multiselect = false;
            dialog.RestoreDirectory = true;
            dialog.CheckFileExists = true;

            if (dialog.ShowDialog() == DialogResult.OK)
            {
                string colladaFile = null;

                if (Path.GetExtension(dialog.FileName).ToLower() == ".dae")
                    colladaFile = dialog.FileName;                    
                else if (Path.GetExtension(dialog.FileName).ToLower() == ".zip")
                    colladaFile = ExtractFilesFromZIP(dialog.FileName);
                else
                {
                    MessageBox.Show("Ungültiges Dateiformat");
                    return;
                }

                StartLoadingModel(colladaFile);
            }            
        }

        void StartLoadingModel(string filename)
        {
            if (loading) return;
            loading = true;
            firstRun = false;

            fileToLoad = filename;
            toolStripProgressBar1.Style = ProgressBarStyle.Marquee;
            toolStripProgressBar1.Value = 100;
            toolStripStatusLabel1.Text = "Loading...";

            if (worker.IsBusy)
            {
                MessageBox.Show("Please wait");
                return;
            }

            worker.RunWorkerAsync();
        }

        private void skeletonToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            modelView.ShowSkeleton = skeletonToolStripMenuItem.Checked;
            modelView.Update();
            modelView.Invalidate();
        }

        private void cornflowerBlueToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
        {
            if (cornflowerBlueToolStripMenuItem.Checked == false)
                return;

            if (modelView != null)
            {
                modelView.BackgroundColor = Microsoft.Xna.Framework.Color.CornflowerBlue;
                modelView.Invalidate();
            }
            
            whiteToolStripMenuItem.Checked = false;
            blackToolStripMenuItem.Checked = false;
        }

        private void whiteToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
        {
            if (whiteToolStripMenuItem.Checked == false)
                return;

            if (modelView != null)
            {
                modelView.BackgroundColor = Microsoft.Xna.Framework.Color.White;
                modelView.Invalidate();
            }
            
            cornflowerBlueToolStripMenuItem.Checked = false;
            blackToolStripMenuItem.Checked = false;
        }

        private void blackToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
        {
            if (blackToolStripMenuItem.Checked == false)
                return;

            if (modelView != null)
            {
                modelView.BackgroundColor = Microsoft.Xna.Framework.Color.Black;
                modelView.Invalidate();
            }

            cornflowerBlueToolStripMenuItem.Checked = false;
            whiteToolStripMenuItem.Checked = false;           
        }

        private void cullingDeaktivierenToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
        {
            modelView.Model.EnableCulling = !cullingDeaktivierenToolStripMenuItem.Checked;

            modelView.Invalidate();
        }
    }
}
